// Copyright Epic Games, Inc. All Rights Reserved.

#include "service.h"

#include <zencore/zencore.h>

#if ZEN_PLATFORM_WINDOWS

#	include <zencore/except.h>
#	include <zencore/thread.h>

#	include <stdio.h>
#	include <tchar.h>
#	include <zencore/windows.h>

#	define SVCNAME L"Zen Store"

SERVICE_STATUS		  gSvcStatus;
SERVICE_STATUS_HANDLE gSvcStatusHandle;
HANDLE				  ghSvcStopEvent = NULL;

void SvcInstall(void);

void ReportSvcStatus(DWORD, DWORD, DWORD);
void SvcReportEvent(LPTSTR);

WindowsService::WindowsService()
{
}

WindowsService::~WindowsService()
{
}

//
// Purpose:
//   Installs a service in the SCM database
//
// Parameters:
//   None
//
// Return value:
//   None
//
VOID
WindowsService::Install()
{
	SC_HANDLE schSCManager;
	SC_HANDLE schService;
	TCHAR	  szPath[MAX_PATH];

	if (!GetModuleFileName(NULL, szPath, MAX_PATH))
	{
		printf("Cannot install service (%d)\n", GetLastError());
		return;
	}

	// Get a handle to the SCM database.

	schSCManager = OpenSCManager(NULL,					  // local computer
								 NULL,					  // ServicesActive database
								 SC_MANAGER_ALL_ACCESS);  // full access rights

	if (NULL == schSCManager)
	{
		printf("OpenSCManager failed (%d)\n", GetLastError());
		return;
	}

	// Create the service

	schService = CreateService(schSCManager,			   // SCM database
							   SVCNAME,					   // name of service
							   SVCNAME,					   // service name to display
							   SERVICE_ALL_ACCESS,		   // desired access
							   SERVICE_WIN32_OWN_PROCESS,  // service type
							   SERVICE_DEMAND_START,	   // start type
							   SERVICE_ERROR_NORMAL,	   // error control type
							   szPath,					   // path to service's binary
							   NULL,					   // no load ordering group
							   NULL,					   // no tag identifier
							   NULL,					   // no dependencies
							   NULL,					   // LocalSystem account
							   NULL);					   // no password

	if (schService == NULL)
	{
		printf("CreateService failed (%d)\n", GetLastError());
		CloseServiceHandle(schSCManager);
		return;
	}
	else
		printf("Service installed successfully\n");

	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
}

void
WindowsService::Delete()
{
	SC_HANDLE schSCManager;
	SC_HANDLE schService;

	// Get a handle to the SCM database.

	schSCManager = OpenSCManager(NULL,					  // local computer
								 NULL,					  // ServicesActive database
								 SC_MANAGER_ALL_ACCESS);  // full access rights

	if (NULL == schSCManager)
	{
		printf("OpenSCManager failed (%d)\n", GetLastError());
		return;
	}

	// Get a handle to the service.

	schService = OpenService(schSCManager,	// SCM database
							 SVCNAME,		// name of service
							 DELETE);		// need delete access

	if (schService == NULL)
	{
		printf("OpenService failed (%d)\n", GetLastError());
		CloseServiceHandle(schSCManager);
		return;
	}

	// Delete the service.

	if (!DeleteService(schService))
	{
		printf("DeleteService failed (%d)\n", GetLastError());
	}
	else
		printf("Service deleted successfully\n");

	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
}

WindowsService* gSvc;

void WINAPI
CallMain(DWORD, LPSTR*)
{
	gSvc->SvcMain();
}

int
WindowsService::ServiceMain()
{
	zen::SetCurrentThreadName("svc-main");

	gSvc = this;

	SERVICE_TABLE_ENTRY DispatchTable[] = {{(LPWSTR)SVCNAME, (LPSERVICE_MAIN_FUNCTION)&CallMain}, {NULL, NULL}};

	// This call returns when the service has stopped.
	// The process should simply terminate when the call returns.

	if (!StartServiceCtrlDispatcher(DispatchTable))
	{
		const DWORD dwError = zen::GetLastError();

		if (dwError == ERROR_FAILED_SERVICE_CONTROLLER_CONNECT)
		{
			// Not actually running as a service
			gSvc = nullptr;

			zen::SetIsInteractiveSession(true);

			return Run();
		}
		else
		{
			zen::ThrowSystemError(dwError, "StartServiceCtrlDispatcher failed");
		}
	}

	zen::SetIsInteractiveSession(false);

	return zen::ApplicationExitCode();
}

int
WindowsService::SvcMain()
{
	// Register the handler function for the service

	gSvcStatusHandle = RegisterServiceCtrlHandler(SVCNAME, SvcCtrlHandler);

	if (!gSvcStatusHandle)
	{
		SvcReportEvent((LPTSTR)TEXT("RegisterServiceCtrlHandler"));

		return 1;
	}

	// These SERVICE_STATUS members remain as set here

	gSvcStatus.dwServiceType			 = SERVICE_WIN32_OWN_PROCESS;
	gSvcStatus.dwServiceSpecificExitCode = 0;

	// Report initial status to the SCM

	ReportSvcStatus(SERVICE_START_PENDING, NO_ERROR, 3000);

	// Create an event. The control handler function, SvcCtrlHandler,
	// signals this event when it receives the stop control code.

	ghSvcStopEvent = CreateEvent(NULL,	 // default security attributes
								 TRUE,	 // manual reset event
								 FALSE,	 // not signaled
								 NULL);	 // no name

	if (ghSvcStopEvent == NULL)
	{
		ReportSvcStatus(SERVICE_STOPPED, GetLastError(), 0);

		return 1;
	}

	// Report running status when initialization is complete.

	ReportSvcStatus(SERVICE_RUNNING, NO_ERROR, 0);

	int ReturnCode = Run();

	ReportSvcStatus(SERVICE_STOPPED, NO_ERROR, 0);

	return ReturnCode;
}

//
// Purpose:
//   Retrieves and displays the current service configuration.
//
// Parameters:
//   None
//
// Return value:
//   None
//
void
DoQuerySvc()
{
	SC_HANDLE			   schSCManager{};
	SC_HANDLE			   schService{};
	LPQUERY_SERVICE_CONFIG lpsc{};
	LPSERVICE_DESCRIPTION  lpsd{};
	DWORD				   dwBytesNeeded{}, cbBufSize{}, dwError{};

	// Get a handle to the SCM database.

	schSCManager = OpenSCManager(NULL,					  // local computer
								 NULL,					  // ServicesActive database
								 SC_MANAGER_ALL_ACCESS);  // full access rights

	if (NULL == schSCManager)
	{
		printf("OpenSCManager failed (%d)\n", GetLastError());
		return;
	}

	// Get a handle to the service.

	schService = OpenService(schSCManager,			 // SCM database
							 SVCNAME,				 // name of service
							 SERVICE_QUERY_CONFIG);	 // need query config access

	if (schService == NULL)
	{
		printf("OpenService failed (%d)\n", GetLastError());
		CloseServiceHandle(schSCManager);
		return;
	}

	// Get the configuration information.

	if (!QueryServiceConfig(schService, NULL, 0, &dwBytesNeeded))
	{
		dwError = GetLastError();
		if (ERROR_INSUFFICIENT_BUFFER == dwError)
		{
			cbBufSize = dwBytesNeeded;
			lpsc	  = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, cbBufSize);
		}
		else
		{
			printf("QueryServiceConfig failed (%d)", dwError);
			goto cleanup;
		}
	}

	if (!QueryServiceConfig(schService, lpsc, cbBufSize, &dwBytesNeeded))
	{
		printf("QueryServiceConfig failed (%d)", GetLastError());
		goto cleanup;
	}

	if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, NULL, 0, &dwBytesNeeded))
	{
		dwError = GetLastError();
		if (ERROR_INSUFFICIENT_BUFFER == dwError)
		{
			cbBufSize = dwBytesNeeded;
			lpsd	  = (LPSERVICE_DESCRIPTION)LocalAlloc(LMEM_FIXED, cbBufSize);
		}
		else
		{
			printf("QueryServiceConfig2 failed (%d)", dwError);
			goto cleanup;
		}
	}

	if (!QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, (LPBYTE)lpsd, cbBufSize, &dwBytesNeeded))
	{
		printf("QueryServiceConfig2 failed (%d)", GetLastError());
		goto cleanup;
	}

	// Print the configuration information.

	_tprintf(TEXT("%s configuration: \n"), SVCNAME);
	_tprintf(TEXT("  Type: 0x%x\n"), lpsc->dwServiceType);
	_tprintf(TEXT("  Start Type: 0x%x\n"), lpsc->dwStartType);
	_tprintf(TEXT("  Error Control: 0x%x\n"), lpsc->dwErrorControl);
	_tprintf(TEXT("  Binary path: %s\n"), lpsc->lpBinaryPathName);
	_tprintf(TEXT("  Account: %s\n"), lpsc->lpServiceStartName);

	if (lpsd->lpDescription != NULL && lstrcmp(lpsd->lpDescription, TEXT("")) != 0)
		_tprintf(TEXT("  Description: %s\n"), lpsd->lpDescription);
	if (lpsc->lpLoadOrderGroup != NULL && lstrcmp(lpsc->lpLoadOrderGroup, TEXT("")) != 0)
		_tprintf(TEXT("  Load order group: %s\n"), lpsc->lpLoadOrderGroup);
	if (lpsc->dwTagId != 0)
		_tprintf(TEXT("  Tag ID: %d\n"), lpsc->dwTagId);
	if (lpsc->lpDependencies != NULL && lstrcmp(lpsc->lpDependencies, TEXT("")) != 0)
		_tprintf(TEXT("  Dependencies: %s\n"), lpsc->lpDependencies);

	LocalFree(lpsc);
	LocalFree(lpsd);

cleanup:
	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
}

//
// Purpose:
//   Disables the service.
//
// Parameters:
//   None
//
// Return value:
//   None
//
void
DoDisableSvc()
{
	SC_HANDLE schSCManager;
	SC_HANDLE schService;

	// Get a handle to the SCM database.

	schSCManager = OpenSCManager(NULL,					  // local computer
								 NULL,					  // ServicesActive database
								 SC_MANAGER_ALL_ACCESS);  // full access rights

	if (NULL == schSCManager)
	{
		printf("OpenSCManager failed (%d)\n", GetLastError());
		return;
	}

	// Get a handle to the service.

	schService = OpenService(schSCManager,			  // SCM database
							 SVCNAME,				  // name of service
							 SERVICE_CHANGE_CONFIG);  // need change config access

	if (schService == NULL)
	{
		printf("OpenService failed (%d)\n", GetLastError());
		CloseServiceHandle(schSCManager);
		return;
	}

	// Change the service start type.

	if (!ChangeServiceConfig(schService,		 // handle of service
							 SERVICE_NO_CHANGE,	 // service type: no change
							 SERVICE_DISABLED,	 // service start type
							 SERVICE_NO_CHANGE,	 // error control: no change
							 NULL,				 // binary path: no change
							 NULL,				 // load order group: no change
							 NULL,				 // tag ID: no change
							 NULL,				 // dependencies: no change
							 NULL,				 // account name: no change
							 NULL,				 // password: no change
							 NULL))				 // display name: no change
	{
		printf("ChangeServiceConfig failed (%d)\n", GetLastError());
	}
	else
		printf("Service disabled successfully.\n");

	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
}

//
// Purpose:
//   Enables the service.
//
// Parameters:
//   None
//
// Return value:
//   None
//
VOID __stdcall DoEnableSvc()
{
	SC_HANDLE schSCManager;
	SC_HANDLE schService;

	// Get a handle to the SCM database.

	schSCManager = OpenSCManager(NULL,					  // local computer
								 NULL,					  // ServicesActive database
								 SC_MANAGER_ALL_ACCESS);  // full access rights

	if (NULL == schSCManager)
	{
		printf("OpenSCManager failed (%d)\n", GetLastError());
		return;
	}

	// Get a handle to the service.

	schService = OpenService(schSCManager,			  // SCM database
							 SVCNAME,				  // name of service
							 SERVICE_CHANGE_CONFIG);  // need change config access

	if (schService == NULL)
	{
		printf("OpenService failed (%d)\n", GetLastError());
		CloseServiceHandle(schSCManager);
		return;
	}

	// Change the service start type.

	if (!ChangeServiceConfig(schService,			// handle of service
							 SERVICE_NO_CHANGE,		// service type: no change
							 SERVICE_DEMAND_START,	// service start type
							 SERVICE_NO_CHANGE,		// error control: no change
							 NULL,					// binary path: no change
							 NULL,					// load order group: no change
							 NULL,					// tag ID: no change
							 NULL,					// dependencies: no change
							 NULL,					// account name: no change
							 NULL,					// password: no change
							 NULL))					// display name: no change
	{
		printf("ChangeServiceConfig failed (%d)\n", GetLastError());
	}
	else
		printf("Service enabled successfully.\n");

	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
}
//
// Purpose:
//   Updates the service description to "This is a test description".
//
// Parameters:
//   None
//
// Return value:
//   None
//
void
DoUpdateSvcDesc()
{
	SC_HANDLE			schSCManager;
	SC_HANDLE			schService;
	SERVICE_DESCRIPTION sd;
	TCHAR				szDesc[] = TEXT("This is a test description");

	// Get a handle to the SCM database.

	schSCManager = OpenSCManager(NULL,					  // local computer
								 NULL,					  // ServicesActive database
								 SC_MANAGER_ALL_ACCESS);  // full access rights

	if (NULL == schSCManager)
	{
		printf("OpenSCManager failed (%d)\n", GetLastError());
		return;
	}

	// Get a handle to the service.

	schService = OpenService(schSCManager,			  // SCM database
							 SVCNAME,				  // name of service
							 SERVICE_CHANGE_CONFIG);  // need change config access

	if (schService == NULL)
	{
		printf("OpenService failed (%d)\n", GetLastError());
		CloseServiceHandle(schSCManager);
		return;
	}

	// Change the service description.

	sd.lpDescription = szDesc;

	if (!ChangeServiceConfig2(schService,				   // handle to service
							  SERVICE_CONFIG_DESCRIPTION,  // change: description
							  &sd))						   // new description
	{
		printf("ChangeServiceConfig2 failed\n");
	}
	else
		printf("Service description updated successfully.\n");

	CloseServiceHandle(schService);
	CloseServiceHandle(schSCManager);
}

//
// Purpose:
//   Sets the current service status and reports it to the SCM.
//
// Parameters:
//   dwCurrentState - The current state (see SERVICE_STATUS)
//   dwWin32ExitCode - The system error code
//   dwWaitHint - Estimated time for pending operation,
//     in milliseconds
//
// Return value:
//   None
//
VOID
ReportSvcStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode, DWORD dwWaitHint)
{
	static DWORD dwCheckPoint = 1;

	// Fill in the SERVICE_STATUS structure.

	gSvcStatus.dwCurrentState  = dwCurrentState;
	gSvcStatus.dwWin32ExitCode = dwWin32ExitCode;
	gSvcStatus.dwWaitHint	   = dwWaitHint;

	if (dwCurrentState == SERVICE_START_PENDING)
		gSvcStatus.dwControlsAccepted = 0;
	else
		gSvcStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP;

	if ((dwCurrentState == SERVICE_RUNNING) || (dwCurrentState == SERVICE_STOPPED))
		gSvcStatus.dwCheckPoint = 0;
	else
		gSvcStatus.dwCheckPoint = dwCheckPoint++;

	// Report the status of the service to the SCM.
	SetServiceStatus(gSvcStatusHandle, &gSvcStatus);
}

void
WindowsService::SvcCtrlHandler(DWORD dwCtrl)
{
	// Handle the requested control code.
	//
	//   Called by SCM whenever a control code is sent to the service
	//   using the ControlService function.

	switch (dwCtrl)
	{
		case SERVICE_CONTROL_STOP:
			ReportSvcStatus(SERVICE_STOP_PENDING, NO_ERROR, 0);

			// Signal the service to stop.

			SetEvent(ghSvcStopEvent);
			zen::RequestApplicationExit(0);

			ReportSvcStatus(gSvcStatus.dwCurrentState, NO_ERROR, 0);
			return;

		case SERVICE_CONTROL_INTERROGATE:
			break;

		default:
			break;
	}
}

//
// Purpose:
//   Logs messages to the event log
//
// Parameters:
//   szFunction - name of function that failed
//
// Return value:
//   None
//
// Remarks:
//   The service must have an entry in the Application event log.
//
VOID
SvcReportEvent(LPTSTR szFunction)
{
	ZEN_UNUSED(szFunction);

	// HANDLE	hEventSource;
	// LPCTSTR lpszStrings[2];
	// TCHAR	Buffer[80];

	// hEventSource = RegisterEventSource(NULL, SVCNAME);

	// if (NULL != hEventSource)
	//{
	//	StringCchPrintf(Buffer, 80, TEXT("%s failed with %d"), szFunction, GetLastError());

	//	lpszStrings[0] = SVCNAME;
	//	lpszStrings[1] = Buffer;

	//	ReportEvent(hEventSource,		  // event log handle
	//				EVENTLOG_ERROR_TYPE,  // event type
	//				0,					  // event category
	//				SVC_ERROR,			  // event identifier
	//				NULL,				  // no security identifier
	//				2,					  // size of lpszStrings array
	//				0,					  // no binary data
	//				lpszStrings,		  // array of strings
	//				NULL);				  // no binary data

	//	DeregisterEventSource(hEventSource);
	//}
}

#endif	// ZEN_PLATFORM_WINDOWS
